//
// Created by Administrator on 2022/1/16.
//

#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "Common/Utils.h"
#include "glm/glm.hpp"
#include "glm/gtc/type_ptr.hpp"

#define numVAOs 1
#define numVBOs 2
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint brickTexture;
GLuint renderingProgram;
GLint vpMatrixLoc;
GLint objectToWorldMatrixLoc;
glm::vec3 cameraPos;
glm::mat4 projMatrix;
glm::mat4 viewMatrix;
glm::mat4 cubeModelMatrix;

void Init(GLFWwindow* window);
void Update(GLFWwindow* window, double currentTime);
void Render();

void OnWindowSizeCallback(GLFWwindow* window, int w, int h);

void SetupVertices();

int main()
{
    if(!glfwInit())
    {
        exit(EXIT_FAILURE);
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(800, 600, "Chapter5", nullptr, nullptr);
    if(!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }
    glfwMakeContextCurrent(window);
    glfwSetWindowSizeCallback(window, OnWindowSizeCallback);

    if(!gladLoadGLLoader(GLADloadproc(glfwGetProcAddress)))
    {
        glfwDestroyWindow(window);
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    Init(window);
    glfwSwapInterval(1);
    while(!glfwWindowShouldClose(window))
    {
        Update(window, glfwGetTime());
        Render();
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();

    exit(EXIT_SUCCESS);
}

void Init(GLFWwindow* window)
{
    renderingProgram = Utils::CreateShaderProgram("../../Chapter5/vertShader.glsl",
                                                  "../../Chapter5/fragShader.glsl");

    cameraPos = glm::vec3(0.0f, 0.0f, 8.0f);
    SetupVertices();

    brickTexture = Utils::LoadTexture("../../Chapter5/wall.jpg");

    int w, h;
    glfwGetFramebufferSize(window, &w, &h);
    projMatrix = glm::perspective(Utils::Deg2Rad(60.0f), float(w) / float(h), 0.1f, 1000.0f);
}

void Update(GLFWwindow* window, double currentTime)
{
    viewMatrix = glm::translate(glm::mat4(1.0f), -cameraPos);
    cubeModelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(sin(0.3f * float(currentTime) * 8.0f),
                                                                sin(0.4f * float(currentTime) * 8.0f),
                                                                sin(0.5f * float(currentTime) * 8.0f)));
    cubeModelMatrix = glm::rotate(cubeModelMatrix, sin(1.75f * float(currentTime)), glm::vec3(1.0f, 0.0f, 0.0f));
    cubeModelMatrix = glm::rotate(cubeModelMatrix, sin(0.75f * float(currentTime)), glm::vec3(0.0f, 1.0f, 0.0f));
}

void Render()
{
    glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(renderingProgram);

    vpMatrixLoc = glGetUniformLocation(renderingProgram, "vpMatrix");
    objectToWorldMatrixLoc = glGetUniformLocation(renderingProgram, "objectToWorldMatrix");

    glm::mat4 vpMatrix = projMatrix * viewMatrix;
    glUniformMatrix4fv(vpMatrixLoc, 1, GL_FALSE, glm::value_ptr(vpMatrix));
    glUniformMatrix4fv(objectToWorldMatrixLoc, 1, GL_FALSE, glm::value_ptr(cubeModelMatrix));

    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(1);

    // texture
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, brickTexture);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT_FACE);

    glDrawArrays(GL_TRIANGLES, 0, 18);
}

void OnWindowSizeCallback(GLFWwindow* window, int w, int h)
{
    glViewport(0, 0, w, h);
    projMatrix = glm::perspective(Utils::Deg2Rad(60.0f), float(w) / float(h), 0.1f, 1000.0f);
}

void SetupVertices()
{
    // 6 tri, 3 vertex per tri, so 6x3 = 18 vertex
    // 3 float per position, so 18 x 3 = 54
    float pyramidPositions[54] = {
            -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // front face
            1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // right face
            1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // back face
            -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // left face
            -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, // base – left front
            1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f
    };
    // 2 float per uv, so 18 x 2 = 36
    float pyramidUVs[36] = {
            0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
            0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
            0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
            0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f
    };

    glGenVertexArrays(numVAOs, vao);
    glBindVertexArray(vao[0]);
    glGenBuffers(numVBOs, vbo);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidPositions), pyramidPositions, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidUVs), pyramidUVs, GL_STATIC_DRAW);
}